﻿using System;
using System.Collections.Generic;
using System.Windows;
using ModelingProjectLib.Interfaces;

namespace INSAwarsWPF
{
    /** ViewModel qui encapsule les unités */
    public class UniteViewModel : ViewModelBase
    {
        /** Model pour l'unité */
        public Unite UniteModel { get; set; }

        /** Case qui contient l'unité */
        public CaseViewModel Conteneur { get; private set; }

        /** Points de mouvement au début du tour ou après le dernier combat */
        public int MvtDebutMouvement { get; private set; }
        /** Case où était l'unité au début du tour ou après le dernier combat */
        public CaseViewModel CaseDebutMouvement { get; private set; }

        /** Vrai ssi on peut annuler le dernier mouvement */
        public bool MouvementAnnulable { get { return MvtDebutMouvement != UniteModel.Mvt; } }

        /** Construit une nouvelle UniteViewModel dans la case _cvm, pour l'unité _unite */
        public UniteViewModel(CaseViewModel _cvm, Unite _unite)
        {
            Conteneur = _cvm;
            UniteModel = _unite;
            initialiserMouvement();

            VillesAttaqueesACeTour = new List<VilleViewModel>();
            UnitesAttaqueesACeTour = new List<UniteViewModel>();
        }

        /** Résumé de la vie de l'unité */
        public string ResumeVie { get { return "(" + UniteModel.Vie + "/" + UniteModel.VieMax + ")"; } }

        /** Tableau des couleurs utilisées pour les joueurs */
        public string Couleur { get { return CaseViewModel.tabCouleur[UniteModel.Possesseur.Num - 1]; } }
        /** Tableau des couleurs réelles utilisées pour les joueurs */
        public string CouleurCode { get { return CaseViewModel.tabCouleurCode[UniteModel.Possesseur.Num - 1]; } }

        /** Chemin vers la texture de l'unite */
        public string CheminImage
        {
            get
            {
                if (UniteModel.Type == "Directeur") return "/INSAwarsWPF;component/Images/Unites/directeur_" + Couleur + ".png";
                else if (UniteModel.Type == "Enseignant") return "/INSAwarsWPF;component/Images/Unites/enseignant_" + Couleur + ".png";
                else return "/INSAwarsWPF;component/Images/Unites/etudiant_" + Couleur + ".png";
            }
        }

        /** Vrai ssi l'unité est sélectionnée */
        private bool isSelected;
        public bool IsSelected
        {
            get { return isSelected; }
            set { isSelected = value; RaisePropertyChanged("IsSelected"); }
        }

        /** Vrai ssi l'unité appartient au joueur courant */
        public bool IsPossedeeParJoueurCourant
        { get { return Conteneur.Conteneur.PartieCourante.JoueurCourant == UniteModel.Possesseur; } }
        /** Vrai ssi l'unité n'appartient pas au joueur courant */
        public bool IsNotPossedeeParJoueurCourant
        { get { return Conteneur.Conteneur.PartieCourante.JoueurCourant != UniteModel.Possesseur; } }

        /** Vrai ssi l'unité est un enseignant */
        public bool IsEnseignant { get { return UniteModel is Enseignant; } }

        /** Vrai ssi l'unité peut créer une ville */
        public bool PeutCreerVille
        {
            get
            {
                return IsEnseignant
                && !Conteneur.HasVille
                && !Conteneur.IsSousInfluenceDeVille
                && UniteModel.Mvt > 0;
            }
        }

        /** Liste des Unites attaquées à ce tour par cette unité */
        public List<UniteViewModel> UnitesAttaqueesACeTour { get; private set; }
        /** Liste des Villes attaquées à ce tour par cette unité */
        public List<VilleViewModel> VillesAttaqueesACeTour { get; private set; }

        /** Tue l'unité : la supprime de la carte, de son joueur et des endroits
         * où elle était référencée, dans le cas d'une attaque par attanquant
         * Tue l'UniteModel et l'UniteViewModel */
        public void supprimer(UniteViewModel attaquant)
        {
            // on doit supprimer l'unité de la liste des attaqués
            attaquant.UnitesAttaqueesACeTour.Remove(this);
            supprimer();

        }

        /** Tue l'unité : la supprime de la carte, de son joueur et des endroits où elle était référencée.
         * Tue l'UniteModel et l'UniteViewModel. Vérifie si le joueur n'est pas mort et si la partie n'est pas finie */
        public void supprimer()
        {
            // on doit supprimer l'unité du joueur
            foreach (JoueurViewModel jvm in Conteneur.Conteneur.Joueurs)
            {
                if (jvm.DerniereUniteSelectionnee == this)
                    jvm.DerniereUniteSelectionnee = null;
                jvm.Unites.Remove(this);
            }

            // on supprime pas forcément l'unité du joueur courant
            Conteneur.Conteneur.Joueurs.getJoueurVMFromJoueur(UniteModel.Possesseur).Unites.Remove(this);

            Conteneur.Unites.Remove(this);
            if (Conteneur.IsSelected) Conteneur.SelectedUnite = null;

            Conteneur.Conteneur.Joueurs.JoueurCourant.majJoueurApresDeplacementUnite();

            if (!Conteneur.Conteneur.Joueurs.joueurIsEnVie(UniteModel.Possesseur))
                Conteneur.Conteneur.Joueurs.supprimerJoueur(UniteModel.Possesseur);

            Conteneur = null;

            // mise à jour du modèle
            UniteModel.Possesseur.Civilisation.ListUnites.Remove(UniteModel);
            UniteModel = null;
        }

        /** Met à jour l'unité après son déplacement */
        protected void majUniteApresDeplacement()
        {
            int indNew = UniteModel.ColumnPosition + Conteneur.Conteneur.PartieCourante.CartePartie.Taille * UniteModel.RowPosition;
            CaseViewModel caseOld = Conteneur;
            CaseViewModel caseNew = Conteneur.Conteneur.Cases[indNew];

            caseOld.Unites.Remove(this);
            caseNew.Unites.Add(this);

            Conteneur = caseNew;
            Conteneur.Conteneur.SelectedCase = caseNew;
            caseNew.SelectedUnite = this;

            //MouvementAnnulable = deplacementAnnulable;

            Conteneur.Conteneur.Joueurs.JoueurCourant.majJoueurApresDeplacementUnite();

            RaisePropertyChanged("PeutCreerVille"); // utile ?
        }

        /** Vérifie si le mouvement de l'unité est possible */
        public bool checkMouvementPossible(CaseViewModel destination)
        {
            return UniteModel.Mvt > 0 && IsPossedeeParJoueurCourant && destination != null;
        }

        /** Déplace l'unité vers le haut et la fait combattre si nécessaire */
        public void deplacerHaut()
        {
            CaseViewModel destination = Conteneur.getCaseRelative(-1, 0);

            if (checkMouvementPossible(destination) && UniteModel.RowPosition > 0)
            {
                if (destination.HasUniteEnnemie)
                {
                    int resultatCombat = combat(destination);

                    if (resultatCombat == 1)
                    {
                        if (!destination.HasUniteEnnemie && !destination.HasVilleEnnemie)
                            UniteModel.RowPosition--;
                        majUniteApresDeplacement();
                    }
                    else if (resultatCombat == 0) majUniteApresDeplacement();
                }
                else if (destination.HasVilleEnnemie)
                {
                    int resultatCombat = combatVille(destination);

                    if (resultatCombat == 1)
                    {
                        if (!destination.HasUniteEnnemie && !destination.HasVilleEnnemie)
                        UniteModel.RowPosition--;
                        majUniteApresDeplacement();
                    }
                    else if (resultatCombat == 0) majUniteApresDeplacement();
                }
                else if (!destination.Pleine)
                {
                    UniteModel.Mvt--;
                    UniteModel.RowPosition--;
                    majUniteApresDeplacement();
                }
            }
        }

        /** Déplace l'unité vers le bas et la fait combattre si nécessaire */
        public void deplacerBas()
        {
            CaseViewModel destination = Conteneur.getCaseRelative(1, 0);

            if (checkMouvementPossible(destination) && UniteModel.RowPosition < Conteneur.Conteneur.PartieCourante.CartePartie.Taille - 1)
            {
                if (destination.HasUniteEnnemie)
                {
                    int resultatCombat = combat(destination);

                    if (resultatCombat == 1)
                    {
                        if (!destination.HasUniteEnnemie && !destination.HasVilleEnnemie)
                        UniteModel.RowPosition++;
                        majUniteApresDeplacement();
                    }
                    else if (resultatCombat == 0) majUniteApresDeplacement();
                }
                else if (destination.HasVilleEnnemie)
                {
                    int resultatCombat = combatVille(destination);

                    if (resultatCombat == 1)
                    {
                        if (!destination.HasUniteEnnemie && !destination.HasVilleEnnemie)
                        UniteModel.RowPosition++;
                        majUniteApresDeplacement();
                    }
                    else if (resultatCombat == 0) majUniteApresDeplacement();
                }
                else if (!destination.Pleine)
                {
                    UniteModel.Mvt--;
                    UniteModel.RowPosition++;
                    majUniteApresDeplacement();
                }
            }
        }

        /** Déplace l'unité vers la gauche et la fait combattre si nécessaire */
        public void deplacerGauche()
        {
            CaseViewModel destination = Conteneur.getCaseRelative(0, -1);

            if (checkMouvementPossible(destination) && UniteModel.ColumnPosition > 0)
            {
                if (destination.HasUniteEnnemie)
                {
                    int resultatCombat = combat(destination);

                    if (resultatCombat == 1)
                    {
                        if (!destination.HasUniteEnnemie && !destination.HasVilleEnnemie)
                        UniteModel.ColumnPosition--;
                        majUniteApresDeplacement();
                    }
                    else if (resultatCombat == 0) majUniteApresDeplacement();
                }
                else if (destination.HasVilleEnnemie)
                {
                    int resultatCombat = combatVille(destination);

                    if (resultatCombat == 1)
                    {
                        if (!destination.HasUniteEnnemie && !destination.HasVilleEnnemie)
                        UniteModel.ColumnPosition--;
                        majUniteApresDeplacement();
                    }
                    else if (resultatCombat == 0) majUniteApresDeplacement();
                }
                else if (!destination.Pleine)
                {
                    UniteModel.Mvt--;
                    UniteModel.ColumnPosition--;
                    majUniteApresDeplacement();
                }
            }
        }

        /** Déplace l'unité vers la droite et la fait combattre si nécessaire */
        public void deplacerDroite()
        {
            CaseViewModel destination = Conteneur.getCaseRelative(0, 1);

            if (checkMouvementPossible(destination) && UniteModel.ColumnPosition < Conteneur.Conteneur.PartieCourante.CartePartie.Taille - 1)
            {
                if (destination.HasUniteEnnemie)
                {
                    int resultatCombat = combat(destination);

                    if (resultatCombat == 1)
                    {
                        if (!destination.HasUniteEnnemie && !destination.HasVilleEnnemie)
                        UniteModel.ColumnPosition++;
                        majUniteApresDeplacement();
                    }
                    else if (resultatCombat == 0) majUniteApresDeplacement();
                }
                else if (destination.HasVilleEnnemie)
                {
                    int resultatCombat = combatVille(destination);

                    if (resultatCombat == 1)
                    {
                        if (!destination.HasUniteEnnemie && !destination.HasVilleEnnemie)
                        UniteModel.ColumnPosition++;
                        majUniteApresDeplacement();
                    }
                    else if (resultatCombat == 0) majUniteApresDeplacement();
                }
                else if (!destination.Pleine)
                {
                    UniteModel.Mvt--;
                    UniteModel.ColumnPosition++;
                    majUniteApresDeplacement();
                }
            }
        }

        /** Récupère l'unité cible sur la case caseCible (l'unité qui a la meilleure défense) */
        private UniteViewModel getUniteCible(CaseViewModel caseCible)
        {
            double defMax = 0;
            UniteViewModel uniteCible = null;

            foreach (UniteViewModel uvm in caseCible.Unites)
            {
                // on a forcément une unité ennemie dans la caseCible (vérifié avant)
                if (uvm.UniteModel.DefenseApparente > defMax)
                {
                    defMax = uvm.UniteModel.DefenseApparente;
                    uniteCible = uvm;
                }
            }

            return uniteCible;
        }

        /** Fonction de combat : l'unité this attaque la case caseCible :
         * calcul de l'unité défensives et appel de l'algo de combat
         * 
         * return -1 si attaquant mort ou si ne se passe rien
         * return 0 si attaquant et attaqué en vie
         * return 1 si attaqué mort (on doit bouger l'attaquant)
         */
        public int combat(CaseViewModel caseCible)
        {
            if (UniteModel.Att == 0)
            {
                MessageBox.Show("L'unité ne peut pas attaquer !");
                return -1;
            }

            // on détermine l'unité cible : qui a la plus grosse défense
            UniteViewModel uniteCible = getUniteCible(caseCible);


            if (UnitesAttaqueesACeTour.Contains(uniteCible)) // on a déjà attaqué l'unité à ce tour
                return -1;


            // si la cible ne peut pas se défendre, on la tue
            if (uniteCible.UniteModel.Def == 0)
            {
                uniteCible.supprimer(this); // this est l'attaquant
                return 1;
            }

            // on lance le dé pour le nombre de tours d'affrontement
            Random randomGen = new Random();
            int maxVie = (UniteModel.Vie < uniteCible.UniteModel.Vie) ? uniteCible.UniteModel.Vie : UniteModel.Vie;
            int nbTours = randomGen.Next(3, maxVie + 2); // Nombre de manche
            //MessageBox.Show("randomNumber : " + randomNumber + "");

            int vieMoiDebut = UniteModel.Vie;
            int vieCibleDebut = uniteCible.UniteModel.Vie;

            for (int i = 0; i < nbTours && UniteModel.Vie > 0 && uniteCible.UniteModel.Vie > 0; i++)
            {
                tourDeCombat(uniteCible, randomGen);
            }

            if (UniteModel.Vie > 0 && uniteCible.UniteModel.Vie > 0)
            {
                UniteModel.Mvt--;
                initialiserMouvement();
                UnitesAttaqueesACeTour.Add(uniteCible);
                MessageBox.Show("Vous avez perdu " + (vieMoiDebut - UniteModel.Vie) + " PV.\n"
                    + "L'ennemi a perdu " + (vieCibleDebut - uniteCible.UniteModel.Vie) + " PV.",
                    "Résultat du combat");
                return 0;
            }
            else if (UniteModel.Vie > 0)
            {
                UniteModel.Mvt--;
                initialiserMouvement();
                MessageBox.Show("L'ennemi est mort !\n"
                    + "Vous avez perdu " + (vieMoiDebut - UniteModel.Vie) + " PV.",
                    "Résultat du combat");
                uniteCible.supprimer(this);
                return 1;
            }
            else
            {
                MessageBox.Show("L'ennemi vous a tué...",
                    "Résultat du combat");
                this.supprimer();
                return -1;
            }
        }

        /** Fonction qui calcule un tour du combat entre l'unité courante et l'unité cible
         * params: uniteCible, randomGen pour la génération des nombres aléatoires */
        private void tourDeCombat(UniteViewModel uniteCible, Random randomGen)
        {
            // actions des directeurs
            double attaque = (double)UniteModel.AttaqueApparente;
            if (Conteneur.HasDirecteur) attaque += attaque / 2.0;

            double defense = (double)uniteCible.UniteModel.DefenseApparente;
            if (uniteCible.Conteneur.HasDirecteur) defense += defense / 2.0;

            double ratio, pourcentageDePerdre, randomDouble;

            if (attaque / defense <= 1)
            {
                ratio = 1.0 - (attaque / defense);
                pourcentageDePerdre = 0.5 + 0.5 * ratio;
            }
            else
            {
                ratio = 1.0 - (defense / attaque);
                pourcentageDePerdre = 0.5 - 0.5 * ratio;
            }

            randomDouble = randomGen.NextDouble();// Entre 0 et 1 non inclu

            if (randomDouble <= pourcentageDePerdre)
            {
                UniteModel.Vie--;
                //MessageBox.Show("L'attaquant est touché !");
            }
            else
            {
                uniteCible.UniteModel.Vie--;
                //MessageBox.Show("L'attaqué est touché !");
            }
        }

        /** Fonction de combat : l'unité this attaque la case caseCible :
         * calcul de l'unité défensives et appel de l'algo de combat
         * 
         * return -1 si attaquant mort ou si ne se passe rien
         * return 0 si attaquant et ville attaquée en vie
         * return 1 si attaqué mort (on doit bouger l'attaquant)
         */
        public int combatVille(CaseViewModel caseCible)
        {
            if (UniteModel.Att == 0)
            {
                MessageBox.Show("L'unité ne peut pas attaquer !");
                return 0;
            }
            VilleViewModel villeCible = caseCible.VilleCase;

            /* should'nt happen
            if (villeCible.VilleModel.Def == 0)
            {
                villeCible.supprimer();
                return 1;
            }
            */

            if (VillesAttaqueesACeTour.Contains(villeCible)) // on a déjà attaqué l'unité à ce tour
                return 0;

            // quand on attaque une ville on prend le max de vie de l'unité comme borne
            Random randomGen = new Random();
            int nbTours = randomGen.Next(3, UniteModel.VieMax + 2); // Nombre de manche


            int vieMoiDebut = UniteModel.Vie;
            int vieCibleDebut = villeCible.VilleModel.Vie;

            for (int i = 0; i < nbTours && UniteModel.Vie > 0 && villeCible.VilleModel.Vie > 0; i++)
            {
                tourDeCombatVille(villeCible, randomGen);
            }

            if (UniteModel.Vie > 0 && villeCible.VilleModel.Vie > 0)
            {
                UniteModel.Mvt--;
                initialiserMouvement();
                VillesAttaqueesACeTour.Add(villeCible);
                MessageBox.Show("Vous avez perdu " + (vieMoiDebut - UniteModel.Vie) + " PV.\n"
                    + "La ville ennemie a perdu " + (vieCibleDebut - villeCible.VilleModel.Vie) + " PV.",
                    "Résultat de l'assaut");
                return 0;
            }
            else if (UniteModel.Vie > 0)
            {
                UniteModel.Mvt--;
                initialiserMouvement();

                MessageBox.Show("Vous avez détruit la ville ennemie !\n"
                    + "Vous avez perdu " + (vieMoiDebut - UniteModel.Vie) + " PV.",
                    "Résultat du combat");
                villeCible.supprimer(this);
                return 1;
            }
            else
            {
                MessageBox.Show("Vous êtes mort en assaillant la ville...",
                    "Résultat du combat");
                this.supprimer();
                return -1;
            }
        }

        /** Fonction qui calcule un tour du combat entre l'unité courante et l'unité cible
           * params: villeCible, randomGen pour la génération des nombres aléatoires */
        private void tourDeCombatVille(VilleViewModel villeCible, Random randomGen)
        {
            // actions des directeurs
            double attaque = (double)UniteModel.AttaqueApparente * ((double)UniteModel.Vie / (double)UniteModel.VieMax);
            if (Conteneur.HasDirecteur) attaque += attaque / 2.0;

            double defense = (double)villeCible.VilleModel.Def * ((double)villeCible.VilleModel.Vie / (double)villeCible.VilleModel.VieMax);
            if (villeCible.Conteneur.HasDirecteur) defense += defense / 2.0;

            double ratio, pourcentageDePerdre, randomDouble;

            if (attaque / defense <= 1)
            {
                ratio = 1.0 - (attaque / defense);
                pourcentageDePerdre = 0.5 + 0.5 * ratio;
            }
            else
            {
                ratio = 1.0 - (defense / attaque);
                pourcentageDePerdre = 0.5 - 0.5 * ratio;
            }

            randomDouble = randomGen.NextDouble();// Entre 0 et 1 non inclu

            if (randomDouble <= pourcentageDePerdre)
            {
                UniteModel.Vie--;
                //MessageBox.Show("L'attaquant est touché !");
            }
            else
            {
                villeCible.VilleModel.Vie--;
                //MessageBox.Show("La ville est touchée !");
            }
        }

        /** Initialise l'unite pour un nouveau tour */
        public void beginNouveauTour()
        {
            UniteModel.Mvt = UniteModel.MvtMax;
            initialiserMouvement();

            UnitesAttaqueesACeTour.Clear();
            VillesAttaqueesACeTour.Clear();
        }

        /** Initialise les mouvements de l'unité au début d'un tour ou après un combat */
        private void initialiserMouvement()
        {
            CaseDebutMouvement = Conteneur;
            MvtDebutMouvement = UniteModel.Mvt;

            RaisePropertyChanged("MouvementAnnulable");
        }

        /** annule le mouvement de l'unité */
        public void annulerMouvement()
        {
            UniteModel.Mvt = MvtDebutMouvement;

            int indNew = CaseDebutMouvement.Column + Conteneur.Conteneur.PartieCourante.CartePartie.Taille * CaseDebutMouvement.Row;
            CaseViewModel caseOld = Conteneur;
            CaseViewModel caseNew = Conteneur.Conteneur.Cases[indNew];

            UniteModel.RowPosition = CaseDebutMouvement.Row;
            UniteModel.ColumnPosition = CaseDebutMouvement.Column;

            caseOld.Unites.Remove(this);
            caseNew.Unites.Add(this);

            Conteneur = caseNew;
            Conteneur.Conteneur.SelectedCase = caseNew;
            caseNew.SelectedUnite = this;

            //UniteModel.MouvementAnnulable = false;


            Conteneur.Conteneur.Joueurs.JoueurCourant.majJoueurApresDeplacementUnite();

            initialiserMouvement();

            // focus clavier
            // fait dans la View

            //RaisePropertyChanged("MouvementAnnulable");
        }
    }
}
